/**
 *	Yggdrasil World Framework Toolkit
 *	(A component of the NeuroCoSA toolkit)
 *	Copyright (C) 2001-5 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween
 * @license	LGPL
 */

package org.stumeikle.Yggdrasil;

import java.net.*;
import java.io.*;
import java.util.*;
import org.stumeikle.NeuroCoSA.DataBlock;


/** A server side wrapper for each connection client. Also could be called a "session".
 *  Runs in its own thread. 20070215 stumeikle. changes to improve throughput and 
 *  remove action watcher
 */
public class Life extends Thread
{
    int			portBase;
    String		creatureType;
    ServerSocket	outputSSocket;
    Socket		outputSocket;
    Socket		inputSocket;
    ObjectOutputStream	oopStream;
    ObjectInputStream	oipStream;
    Object		iMutex;
    boolean		iReady;

    DataBlock		client_senses;
    DataBlock		client_actions;

    /** Constructor
     * @param t is the type of the creature connecting and 
     * @param p is the port that the creature should communicate on . (p and p+1)
     */
    Life(String t, int p)
    {
        // *$ check -> do we need 'actions' now??
	//set things up , ready to be run
	portBase = p;
	creatureType=t;
	client_senses = new DataBlock();
	client_actions= new DataBlock();
	client_actions.setVersion(-5);
	iMutex = new Object();
	iReady = false;
    }

    public void	run()
    {
	openConnections();

	iReady = true;

	do
	{
	    //(1) block this thread until the senses say we're all clear
	    //clear the interrupted state so we don't skip the block next time
	    interrupted();

	    synchronized(iMutex)
	    {
	    try
	    {
		     iMutex.wait();
	    }catch(Exception e) 
	    {
//		System.out.println("Life exception = " + e.getMessage() + " type=" + e);
	    } //no great shakes if we send old data
	    }

	    //(2) ok, body has cleared us. senses to send must be present
	    //send the senses
	    try
	    {
		sendSenses();
// 		System.out.println("Life: successfully sent client senses,v="
// 		 			+client_senses.getVersion());
	     }
	     catch(IOException e )
	     {
	        //this next usually occurs when the client has disconnected.
		//If this is the case, modify actions to request
		//death of the creature (this lets world know that we've
		//died, however world won't remove us until it is
		//notified via FOL that elf life has stopped
		    
		//FIX FIX FIX. we might need to pass a specific control action to the
		//world to kill the connection ...
		ControlBlock cb = new ControlBlock();
		cb.setClientDisconnected(true);
		client_actions = (DataBlock) cb; 

		 //was: client_actions.goToHell();
		System.out.println("Life: unable to send sense info");
		break;
	    }

	    //(3) wait on the actions returning from the client
	    DataBlock	ab;
	    try
	    {
        	ab = (DataBlock) oipStream.readObject();
	    }
	    catch(Exception e)
	    {
		ab= null;
	    }

	    //(4) its not clear to me if (3) will block or not if the data is
	    //not available. it used to in previous versions of java
	    if (ab!=null)
	    {
		client_actions = ab;
	    }
	}while(true);

	try
	{
	oopStream.close();
	outputSocket.close();
	outputSSocket.close();
	oipStream.close();
	inputSocket.close();
	}catch(Exception e){}
    }

    void	openConnections()
    {
	//bind to the given port p and to p+1
	//we have to be clever about this. first create an output socket
        try
        {
	    outputSSocket = new ServerSocket(portBase);

	    //wait for the client to connect to it,
	    //wait for elf
    	    outputSocket = outputSSocket.accept();

	    //then create an input socket which binds to the clients output socket!
	    //keep retrying until we suceed as client might be slow
	    int	c=0;

	    for(c=0;c<10;c++)
	    {
		try
		{inputSocket = new Socket("localhost", portBase+1);}
		catch(Exception e)
    		{
		    //client probably not responding. wait a while then try again
		    try{Thread.sleep(100);}catch(Exception et){}
		    continue;
		}
		break;
	    }

	    if (c==10)
	    {
		IOException	e = new IOException ("time out");
		throw(e);
	    }

	    //(as was) set up the action watcher ->
	    //
	    try	{
	    oipStream = new ObjectInputStream( inputSocket.getInputStream() );
	    }
	    catch(Exception e) {
	    System.out.println("ACTIONWACHER:! problem creating input stream");
	    }
	    //<-

	    System.out.println("Life: server side wrapper is ready!");
	}
	catch (IOException ett)
	{
    	    System.err.println("Life:Accept failed or client time out");
    	    try{outputSSocket.close();}catch(Exception e){}
    	    System.exit(1);//FIX . just kill the thread
	}
	//first set up an object output stream on the senses channel
	try
	{
	    oopStream = new ObjectOutputStream(outputSocket.getOutputStream());
	}catch(IOException e)
	{
	    System.err.println("Life: unable to create output socket");
	}
    }


    void	sendSenses() throws IOException
    {
	//send the sense block to the client
	//copy to a new block, then send that and
	//delete. bugger knows why
	DataBlock s;
	
	//s = client_senses.makeCopy();
	s = (DataBlock) client_senses.clone();
	oopStream.writeObject( s );

	s= null;
	//oopStream.writeObject( client_senses );
	//oopStream.writeObject( client_senses );
    }

    //store the senses. This should only be called by BODY
    //does not need to be synchronised as body will block life thread at
    //appropriate times. 
    void	setSenses( DataBlock s )
    {
    	client_senses = s;
    }

    //this might need to be synchronised, not sure
    DataBlock	getActions()
    {
        //ask the action watcher for the actions
        return client_actions;
    }

    boolean 	isReady()
    { return iReady; }
}
